package server;


import java.io.*;  
import java.net.Socket;
import java.rmi.RemoteException;
import java.util.*; 
import javax.servlet.*; 
import javax.servlet.http.*; 

import com.oreilly.servlet.RemoteDaemonHttpServlet;

public class ChatServlet extends RemoteDaemonHttpServlet implements ChatServer, MessageTypes {
	// source acts as the distributor of new messages 
	MessageSource chatSource = new MessageSource();
	MessageSource annoSource = new MessageSource();

	public void doGet(HttpServletRequest req, HttpServletResponse res) 
			throws ServletException, IOException { 
		// Everything is sent using the POST method. 
	}

	// Main entry point for handling all user traffics. 
	public void doPost(HttpServletRequest req, HttpServletResponse res) 
			throws ServletException, IOException {

		String msgType = req.getParameter(MSG_TYPE_KEY);
		// Ignore malformed messages. 
		if (msgType != null) {
			if (msgType.equals(CHAT_TYPE)){
				processChatMsg (req, res);
			} else if (msgType.equals(ANNO_TYPE)){
				processAnnotationMsg(req, res);
			}
		}		
	}

	private void processAnnotationMsg (HttpServletRequest req, HttpServletResponse res) throws IOException {
		String action = req.getParameter(ACTION_TYPE);
		if (action.equals(MessageTypes.ANNO_PULL)){
			processAnnoPull(req, res);
		} else if (action.equals(ANNO_PUSH)) {
			processAnnoPush(req, res);
		}		  
	}


	private void processAnnoPush (HttpServletRequest req, HttpServletResponse res) {
		// Accept the new message as the "message" parameter 
		String message = req.getParameter(CONTENT);

		// Broadcast it to all listening clients 
		if (message != null) {
			// In case there is someone waiting. 
			broadcastAnnoMessage(message);
		}
		// Set the status code to indicate there will be no response 
		res.setStatus(HttpServletResponse.SC_NO_CONTENT);
	}

	private void processAnnoPull (HttpServletRequest req, HttpServletResponse res) throws IOException {
		res.setContentType("text/plain"); 
		PrintWriter out;
		out = res.getWriter();
		// Return the next message (blocking)
		String message = new MessageSink().getNextMessage(annoSource);
		out.println(message);
	}

	private void processChatMsg (HttpServletRequest req, HttpServletResponse res) throws IOException {
		String action = req.getParameter(ACTION_TYPE);
		if (action.equals(CHAT_PULL)){
			processChatPull(req, res);
		} else if (action.equals(CHAT_PUSH)) {
			processChatPush(req, res);
		}		 
	}

	private void processChatPull (HttpServletRequest req, HttpServletResponse res) throws IOException {
		res.setContentType("text/plain"); 
		PrintWriter out;
		out = res.getWriter();
		// Return the next message (blocking) 
		out.println(getNextMessage()); 
	}

	private void processChatPush (HttpServletRequest req, HttpServletResponse res) {
		res.setContentType("text/plain");

		// Accept the new message as the "message" parameter 
		String message = req.getParameter("message");

		// Broadcast it to all listening clients 
		if (message != null) 
			broadcastChatMessage(message);

		// Set the status code to indicate there will be no response 
		res.setStatus(HttpServletResponse.SC_NO_CONTENT);
	}

	// getNextMessage() returns the next new message. // It blocks until there is one. 
	public String getNextMessage() { 
		// Create a message sink to wait for a new message from the message source. 
		return new MessageSink().getNextMessage(chatSource); 
	}

	// broadcastMessage() informs all currently listening clients that there 
	// is a new message. Causes all calls to getNextMessage() to unblock. 
	public void broadcastChatMessage(String message) { 
		// Send the message to all the HTTP-connected clients by giving the 
		// message to the message source 
		chatSource.sendMessage(message);
	} 

	// MessageSource acts as the source for new messages. 
	// Clients interested in receiving new messages can 
	// observe this object. 

	class MessageSource extends Observable { 
		public void sendMessage(String message) { 
			setChanged(); 
			notifyObservers(message); 
		} 
	}

	// MessageSink acts as the receiver of new messages. 
	// It listens to the source. 
	class MessageSink implements Observer {
		String message = null; 
		// set by update() and read by getNextMessage() 
		// Called by the message source when it gets a new message 
		synchronized public void update(Observable o, Object arg) {
			// Get the new message 
			message = (String)arg; 
			// Wake up our waiting thread 
			notify(); 
		} 
		// Gets the next message sent out from the message source 
		synchronized public String getNextMessage(MessageSource source) { 
			// Tell source we want to be told about new messages 
			source.addObserver(this); 
			// Wait until our update() method receives a message 
			while (message == null) { 
				try { 
					wait(); 
				} catch (Exception ignored) { 
				} 
			} 
			// Tell source to stop telling us about new messages 
			source.deleteObserver(this); 
			// Now return the message we received 
			// But first set the message instance variable to null 
			// so update() and getNextMessage() can be called again. 
			String messageCopy = message; 
			message = null; 
			return messageCopy; 
		} 
	}

	@Override
	public void handleClient(Socket arg0) {
		// TODO Auto-generated method stub
	}

	@Override
	public void broadcastAnnoMessage(String message)  {
		annoSource.sendMessage(message);
	}
}
